package org.idontknow.prey.species;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import java.lang.InstantiationException;
import java.lang.IllegalAccessException;
import java.lang.ClassNotFoundException;

import org.idontknow.prey.exception.*;
import org.idontknow.prey.loader.MyClassLoader;

public class God {

  private static HashMap<String, Species> allSpecies = new HashMap<String, Species>(64, .75f);
  private static HashMap<String, ArrayList<Species>> eats = new HashMap<String, ArrayList<Species>>(32, .75f);
  private static HashMap<String, ArrayList<Species>> eatenBy = new HashMap<String, ArrayList<Species>>(32, .75f);

  private static MyClassLoader loader = new MyClassLoader();

  public static void eats(String predator, String prey) {

    ArrayList<Species> preys = eats.get(predator);
    ArrayList<Species> predators = eatenBy.get(prey);

//    Tiger tiger = new Tiger();
//    predators.add(tiger);

    Species thePrey = allSpecies.get(prey);
    Species thePredator = allSpecies.get(predator); 
//    Species thePrey = new Species(prey);
    try{
      if(thePrey == null) {
        thePrey = (Species)loader.loadClass(prey, true).newInstance();
        allSpecies.put(prey, thePrey);
      }
      if(thePredator == null) {
        thePredator = (Species)loader.loadClass(predator, true).newInstance();
        allSpecies.put(predator, thePredator);
      }
    }catch(ClassNotFoundException e){
      System.out.println("Class " + prey + " not found!");
      e.printStackTrace();
    }catch(InstantiationException e) {
      System.out.println("Instantiation of class " + prey + " failed");
      e.printStackTrace();
    }catch(IllegalAccessException e) {
      System.out.println("Illegal Access to class implementation!");
      e.printStackTrace();
    }

    if(preys == null) {
      ArrayList<Species> newList = new ArrayList<Species>();
      newList.add(thePrey);
      eats.put(predator, newList);
    }
    else {
      int pos = Collections.binarySearch(preys, thePrey);
      if(pos < 0) {
        preys.add(-(pos + 1), thePrey);
        Collections.sort(preys);
      }
    }

    if(predators == null) {
      ArrayList<Species> newList = new ArrayList<Species>();
      newList.add(thePredator);
      eatenBy.put(prey, newList);
    }
    else {
      int pos = Collections.binarySearch(predators, thePredator);
      if(pos < 0) {
        predators.add(-(pos + 1), thePredator);
        Collections.sort(predators);
      }
    }
  }

  public static Species[] whoEats(String prey) throws GoodLuckException {
    ArrayList<Species> predators = eatenBy.get(prey);
    if(predators == null)
      throw new GoodLuckException();
    else
      return predators.toArray(new Species[predators.size()]);
  }

  public static Species[] whoIsEatenBy(String predator) throws BadLuckException {
    ArrayList<Species> preys = eats.get(predator);
    if(preys == null)
      throw new BadLuckException();
    else
      return preys.toArray(new Species[preys.size()]);
  }

  private static final Comparator<Species> comp = new Comparator<Species>() {
    public int compare(Species s1, Species s2) {
      return s1.getWeight() - s2.getWeight();
    } 
  };

  public static Species[] speciesByWeight() throws EmptyWorldException {
    ArrayList<Species> all = new ArrayList<Species>();
    if(all == null)
      throw new EmptyWorldException();
    else {
      for(Species obj: allSpecies.values())
        all.add(obj);
      Collections.sort(all, comp);
      return all.toArray(new Species[all.size()]); 
    }
  }

  public static Species[] whoIsIndirectlyEatenBy(String predator) throws SomeLuckException, BadLuckException {
    ArrayList<Species> dirtyPredators = new ArrayList<Species>();
    ArrayList<Species> result = new ArrayList<Species>();
    dirtyPredators.add(allSpecies.get(predator));

    ArrayList<Species> directPreys = eats.get(predator);
    Species[] temp;

    if(directPreys == null)
      throw new BadLuckException();
    else {
      for(Species obj: directPreys) {
        if(Collections.binarySearch(dirtyPredators, obj) < 0) {
          dirtyPredators.add(obj);
          Collections.sort(dirtyPredators);
          if(eats.get(obj.getName()) != null) {
            try{
              temp = whoIsIndirectlyEatenBy(obj.getName());
              for(Species indirectPrey: temp)
                if(Collections.binarySearch(result, indirectPrey) < 0) {
                  result.add(indirectPrey);
                  Collections.sort(result);
                }
            }catch (Exception e) {
              continue;
            }finally {
              for(Species directPrey: eats.get(obj.getName()))
                if(Collections.binarySearch(result, directPrey) < 0) {
                  result.add(directPrey);
                  Collections.sort(result);
                }
            }
          }
        }
      }
    }
    if(result.size() == 0)
      throw new SomeLuckException();
    else
      return result.toArray(new Species[result.size()]);
  }
}
